In [1]:
# エポック数が10だとトレーニングに時間が掛かりすぎるため、ここでは2にしていますが、
# より高い精度が出ることを体験したい方はより大きなエポック数を試してみてください。
# epochs = 10
epochs = 2
MNISTは手書き文字のデータセットです。CNNを使った分類モデルをトレーニングします。
Federated Learningは分散配置された学習データで学習を行える、とてもエキサイティングで、今まさに盛り上がりつつある機械学習のテクニックです。学習データはデータ所有者(ここではワーカー)の元を離れず、モデルの方がワーカー間で共有されながら学習されていくという考え方です。この手法の利点は、データのプライバシーを守れる事です。アプリケーションの応用例としては、キーボードの予測入力があります。キーボードの予測入力ではあなたが入力するテキストを学習データとする必要がありますが、個人的なメッセージですから、サーバーに送りたくはないですよね!
ところで、Federated Learningが注目を集め初めているのは、個人情報の保護に関する意識の高まりと関係があります。2018年の5月にEUで施工されたGDPRをきっかけに一躍注目を集めるようになりました。法規制を見越して、アップルやグーグルはこの技術に大きな投資をしています。特にモバイルユーザーのプライバシーの保護を意識しています。しかしながら、彼らはソースコードをオープンにしていません。
私たち(OpenMined)は、機械学習に携わる者なら、誰でも簡単にプライバシーに配慮した学習手法にアクセスできるべきだと考えています。そこで私たちはたったの一行のコードでデータを暗号化できるツールを開発しました。また、PyTorch 1.0の新機能を使って、直感的に、セキュアに、かつ大規模にFederated Learningを実装できるフレームワークもリリースしました。 詳細はブログを参照してください
このチュートリアルでは、Pytorchの(公式)チュートリアルをベースに、PySyftのライブラリを使う事で簡単にFederated Learningを実装できる事を紹介します。チュートリアルのコードサンプルを元に、Federated Learning化するために必要な変更を、一行一行確認していきましょう。
このコンテンツは私たちのブログからも見つけることが可能です。
Authors:
Ok, let's get started!
In [2]:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
次にPySyft関連の設定を行います。ここではリモートワーカーとしてalice
とbob
を定義しています。
In [3]:
import syft as sy # <-- NEW: Pysyftライブラリのインポート
hook = sy.TorchHook(torch) # <-- NEW: PyTorchをホック(Federated Learningに必要な機能を追加)
bob = sy.VirtualWorker(hook, id="bob") # <-- NEW: リモートワーカー、Bobを追加
alice = sy.VirtualWorker(hook, id="alice") # <-- NEW: 同じくAliceを追加
学習処理のハイパーパラメータを定義します。
In [4]:
class Arguments():
def __init__(self):
self.batch_size = 64
self.test_batch_size = 1000
self.epochs = epochs
self.lr = 0.01
self.momentum = 0.5
self.no_cuda = False
self.seed = 1
self.log_interval = 30
self.save_model = False
args = Arguments()
use_cuda = not args.no_cuda and torch.cuda.is_available()
torch.manual_seed(args.seed)
device = torch.device("cuda" if use_cuda else "cpu")
kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}
In [5]:
federated_train_loader = sy.FederatedDataLoader( # <-- FederatedDataLoader を使います
datasets.MNIST('../data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
]))
.federate((bob, alice)), # <-- NEW: FederatedDatasetに変換し、分割してワーカーへ送ります。
batch_size=args.batch_size, shuffle=True, **kwargs)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=args.test_batch_size, shuffle=True, **kwargs)
In [6]:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 20, 5, 1)
self.conv2 = nn.Conv2d(20, 50, 5, 1)
self.fc1 = nn.Linear(4*4*50, 500)
self.fc2 = nn.Linear(500, 10)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.max_pool2d(x, 2, 2)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2, 2)
x = x.view(-1, 4*4*50)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x, dim=1)
In [7]:
def train(args, model, device, federated_train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, target) in enumerate(federated_train_loader): # <-- now FederatedDataLoaderです
model.send(data.location) # <-- NEW: モデルをデータ所有者の元へ送ります
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
model.get() # <-- NEW: 学習済みモデルを受け取ります
if batch_idx % args.log_interval == 0:
loss = loss.get() # <-- NEW: ログ表示用にロスを受け取ります
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * args.batch_size, len(federated_train_loader) * args.batch_size,
100. * batch_idx / len(federated_train_loader), loss.item()))
テスト用の関数は変更の必要はありません。
In [8]:
def test(args, model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.nll_loss(output, target, reduction='sum').item() # バッチロスを合計します
pred = output.argmax(1, keepdim=True) # log-probabilityが最大のインデックスを取得します
correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))
In [9]:
%%time
model = Net().to(device)
optimizer = optim.SGD(model.parameters(), lr=args.lr) # TODO momentumは現在サポートされていません
for epoch in range(1, args.epochs + 1):
train(args, model, device, federated_train_loader, optimizer, epoch)
test(args, model, device, test_loader)
if (args.save_model):
torch.save(model.state_dict(), "mnist_cnn.pt")
ジャジャーン! Federated Learningを使ってリモートデータでのモデル学習に成功しました!
見て頂いた通り、Pytorchの公式チュートリアルのソースコードを10行程度変更するだけで、Federated Learningを使ってMNISTを学習することができました。
もちろん、改善の余地はまだまだあります。各ワーカーのコンピューテーションを並列化するとか、バッチ毎に集計をするのではなく、数バッチに1回だけ集計をするようにするとか、ワーカーどうしのやりとりの頻度を減らすとか、色々あります。これらは、Federated Learningをプロダクション環境で使えるようにするために私たちが取り組んでいる機能です。それらの機能がリリースされしだい、チュートリアルにも反映させていきたいと思います。
もし、やろうと思えば、ご自身でもFederated Learningを実装できると思います。もし、PySyft、プライバシーに配慮したディープラーニング、非中央集権的なAIの学習データ、あるいは学習データのサプライチェーンに関する活動に参加したい、貢献したいって思われた方は以下を参考にしてみてください。
一番簡単に貢献できる方法はこのGitHubのレポジトリにスターを付けていただくことです。スターが増えると露出が増え、より多くのデベロッパーにこのクールな技術の事を知って貰えます。
最新の開発状況のトラッキングする一番良い方法はSlackに入ることです。 下記フォームから入る事ができます。 http://slack.openmined.org
コミュニティに貢献する一番良い方法はソースコードのコントリビューターになることです。PySyftのGitHubへアクセスしてIssueのページを開き、"Projects"で検索してみてください。参加し得るプロジェクトの状況を把握することができます。また、"good first issue"とマークされているIssueを探す事でミニプロジェクトを探すこともできます。
もし、ソースコードで貢献できるほどの時間は取れないけど、是非何かサポートしたいという場合は、寄付をしていただくことも可能です。寄附金の全ては、ハッカソンやミートアップの開催といった、コミュニティ運営経費として利用されます。
In [ ]: